#include "stdafx.h"
#include "HXConfig.h"
#include "HXWindow.h"

//=============================================================================

HXPlayerConfig g_config;			// parameters for this program

//=============================================================================

//! Macro that calls a COM method returning HRESULT value
#define HRCALL(a, errmsg) \
do { \
    hr = (a); \
    if (FAILED(hr)) { \
        ANotify(NULL, "%s:%d  HRCALL Failed: %s\n  0x%.8x = %s\n", \
                __FILE__, __LINE__, errmsg, hr, #a ); \
        goto clean; \
    } \
} while (0)

//-----------------------------------------------------------------------------

//! Reset the resource data.
void HXResource::Clear() 
{ 
	modelName.clear(); 
	textureName.clear();
	detailName.clear();
	blendList.clear();
	shininess = -1.0f; 
	alpha_threshold = -1.0f;
	center = true;
	recomp_normal = 1;
	renderIndex = -1; 
	pRender = NULL;
	item = -1;
}

HXResourceGroup::HXResourceGroup()
: name(L""),
  pParent(NULL)
{
}

HXResourceGroup::HXResourceGroup(const wstring& _name, HXResourceGroup* _pParent)
: name(_name),
  pParent(_pParent)
{
}

HXResourceGroup* HXResourceGroup::GetChild(const wstring& _name)
{
	for (list<HXResourceGroup>::iterator res_iter = children.begin(); res_iter != children.end(); ++res_iter)
	{
		if (res_iter->name == _name)
		{
			return &*res_iter;
		}
	}
	return NULL;
}

HXResourceGroup* HXResourceGroup::GetDescendant(const wstring& _name)
{
	HXResourceGroup* pRG = (_name.size() == 0) ? this : GetChild(_name);
	if (!pRG)
	{
		for (list<HXResourceGroup>::iterator res_iter = children.begin(); res_iter != children.end(); ++res_iter)
		{
			if (pRG = res_iter->GetDescendant(_name)) break;
		}
	}
	return pRG;
}

size_t HXResourceGroup::Size()
{
	size_t count = resources.size();
	for (list<HXResourceGroup>::iterator res_iter = children.begin(); res_iter != children.end(); ++res_iter)
	{
		count += res_iter->Size();
	}
	return count;
}

//! get a list of all the resources in this group (including resources of the subgroups)
void HXResourceGroup::Expand(list<HXResource*>& _res)
{
	for (list<HXResourceGroup>::iterator res_iter = children.begin(); res_iter != children.end(); ++res_iter)
	{
		res_iter->Expand(_res);
	}
	for (list<HXResource>::iterator res_iter = resources.begin(); res_iter != resources.end(); ++res_iter)
	{
		_res.push_back(&*res_iter);
	}
}

//! get a list of all the subgroups of this group (including their subgroups)
void HXResourceGroup::ExpandGroup(list<HXResourceGroup*>& _res)
{
	for (list<HXResourceGroup>::iterator res_iter = children.begin(); res_iter != children.end(); ++res_iter)
	{
		res_iter->ExpandGroup(_res);
		_res.push_back(&*res_iter);
	}
}

void HXResourceGroup::RemoveRenderIndex(int _index)
{
	for (list<HXResourceGroup>::iterator chIt = children.begin(); chIt != children.end(); ++chIt)
	{
		chIt->RemoveRenderIndex(_index);
	}
	for (list<HXResource>::iterator resIt = resources.begin(); resIt != resources.end(); ++resIt)
	{
		if (resIt->renderIndex > _index)
			resIt->renderIndex--;
	}
}

void HXResourceGroup::Clear()
{
	children.clear();
	resources.clear();
	transform = HXResourceTransform(); // clear transform
}

//-----------------------------------------------------------------------------

//! Default constructor.
HXPlayerConfig::HXPlayerConfig()
: disableShaders(false),
  enableDepthShaders(false),
  multisample(1)
{
	wchar_t absolutePath[MAX_PATH];
	PathSearchAndQualify(L"../Data/Shaders", absolutePath, MAX_PATH);
	shaderDir.assign(absolutePath);
	Clear();
}

//! Load XML configuration from specified filepath.
bool HXPlayerConfig::LoadFromXML(wstring& xmlstring, bool isFilename, bool paramsOnly, bool* pbExpressionsAreCleared)
{
	bool success = false;
	IXMLDOMDocument *pXMLDom=NULL;
    VARIANT_BOOL status;
    VARIANT var;
	wchar_t* wcopy = NULL;
    HRESULT hr;
	wstring tempParam;
	bool importLoaded;
	size_t importIndex = 0;

	if (isFilename)
	{
		g_window.ShowStatus(L"Applying configuration file \"%s\"...", xmlstring.c_str());
	}
	else
	{
		g_window.ShowStatus(L"Applying configuration XML string...");
	}

	bool bExpressionsAreCleared[4];
	if (pbExpressionsAreCleared == NULL) 
	{
		bExpressionsAreCleared[0] = false; // triggered expressions
		bExpressionsAreCleared[1] = false; // idle expressions
		bExpressionsAreCleared[2] = false; // visemes
		bExpressionsAreCleared[3] = false; // models
		pbExpressionsAreCleared = bExpressionsAreCleared;
	}

    pXMLDom = DomFromCOM();
    if (!pXMLDom) goto clean;

    VariantInit(&var);
	
	if (isFilename)
	{
		var = VariantString(xmlstring.c_str());
		HRCALL(pXMLDom->load(var, &status), "dom->load(): ");
	}
	else
	{
		wcopy = new wchar_t[xmlstring.size()+1];
		for (size_t i = 0; i < xmlstring.size(); i++)
		{
			wcopy[i] = xmlstring[i];
		}
		wcopy[xmlstring.size()] = '\0';
		HRCALL(pXMLDom->loadXML(wcopy, &status), "dom->loadXML(): ");
	}

    if (status!=VARIANT_TRUE) 
	{
          ReportParseError(pXMLDom, "Failed to load HXML configuration.");
          goto clean;
    }

	// import from other HXML/AXML/PXML files
	do
	{
		if (importLoaded = HasNode(pXMLDom, wstring(L"//headx/import"), importIndex))
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/import"), wstring(L"file"), tempParam, importIndex);
			importLoaded = LoadFromXML(tempParam, true, paramsOnly, pbExpressionsAreCleared);
			tempParam.clear();
			importIndex++;
		}
	} while (importLoaded);

	if (HasNode(pXMLDom, wstring(L"//headx/render"), 0))
	{
		// render options
		ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"fov"), tempParam);
		if (!tempParam.empty()) { fov = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"fps"), tempParam);
		if (!tempParam.empty()) { renderFPS = WStringToInt(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"immediate_mode"), tempParam);
		if (!tempParam.empty()) { bImmediateModeRender = WStringToBool(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"use_shaders"), tempParam);
		if (!tempParam.empty()) { bUseShaders = WStringToBool(tempParam); tempParam.clear(); }

		// depth rendering vs multisampling...
		int tempDepth = -1;
		if (maxDepthPasses > 1)
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"max_depth"), tempParam);
			if (!tempParam.empty()) 
			{ 
				tempDepth = WStringToInt(tempParam); 
				tempParam.clear(); 
				if (tempDepth > 1)
				{
					maxDepthPasses = tempDepth;
				}
				else
				{
					tempDepth = -1;
				}
			}
		}
		if (!paramsOnly)
		{
			if (tempDepth == -1)
			{
				ReadAttribute(pXMLDom, wstring(L"//headx/render"), wstring(L"max_depth"), tempParam);
				if (!tempParam.empty()) { maxDepthPasses = WStringToInt(tempParam); tempParam.clear(); }
			}
		}

		// load basic head models
		if (!paramsOnly)
		{
			if (HasNode(pXMLDom, wstring(L"//headx/render/group"), 0) || HasNode(pXMLDom, wstring(L"//headx/render/model"), 0))
			{
				if (!pbExpressionsAreCleared[3])
				{
					resourceGroup.Clear(); // clear only if any are defined
					pbExpressionsAreCleared[3] = true;
				}
				AddModelsFromXML(xmlstring, isFilename, true); // load all models (except imports)
			}

			// load background image
			ReadAttribute(pXMLDom, wstring(L"//headx/render/background"), wstring(L"file"), backgroundName);
		}


		// background colour (if no image)
		ReadAttribute(pXMLDom, wstring(L"//headx/render/background"), wstring(L"red"), tempParam);
		if (!tempParam.empty()) { bgRedness = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/background"), wstring(L"green"), tempParam);
		if (!tempParam.empty()) { bgGreenness = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/background"), wstring(L"blue"), tempParam);
		if (!tempParam.empty()) { bgBlueness = WStringToFloat(tempParam); tempParam.clear(); }

		// load lights
		if (HasNode(pXMLDom, wstring(L"//headx/render/light"), 0))
		{
			wstring lightAmbient, lightDiffuse, lightSpecular, lightAzimuth, lightElevation, lightReset;
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"ambient"), lightAmbient);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"diffuse"), lightDiffuse);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"azimuth"), lightAzimuth);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"elevation"), lightElevation);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"reset"), lightReset);
			light1.Clear();
			if (!lightAmbient.empty()) light1.ambient = WStringToFloat(lightAmbient);
			if (!lightDiffuse.empty()) light1.diffuse = WStringToFloat(lightDiffuse);
			if (!lightAzimuth.empty()) light1.azimuth = WStringToFloat(lightAzimuth);
			if (!lightElevation.empty()) light1.elevation = WStringToFloat(lightElevation);
			if (!lightReset.empty()) light1.reset = WStringToBool(lightReset);
		}
		if (HasNode(pXMLDom, wstring(L"//headx/render/light2"), 0))
		{
			wstring lightAmbient, lightDiffuse, lightSpecular, lightAzimuth, lightElevation, lightReset;
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light2"), wstring(L"ambient"), lightAmbient);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light2"), wstring(L"diffuse"), lightDiffuse);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light2"), wstring(L"azimuth"), lightAzimuth);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light2"), wstring(L"elevation"), lightElevation);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/light"), wstring(L"reset"), lightReset);
			light2.Clear();
			if (!lightAmbient.empty()) light2.ambient = WStringToFloat(lightAmbient);
			if (!lightDiffuse.empty()) light2.diffuse = WStringToFloat(lightDiffuse);
			if (!lightAzimuth.empty()) light2.azimuth = WStringToFloat(lightAzimuth);
			if (!lightElevation.empty()) light2.elevation = WStringToFloat(lightElevation);
			if (!lightReset.empty()) light2.reset = WStringToBool(lightReset);
		}

		// load subtitle options
		if (HasNode(pXMLDom, wstring(L"//headx/render/subtitle"), 0))
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"output_show"), tempParam);
			if (!tempParam.empty()) { bSubtitleOutput = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"input_show"), tempParam);
			if (!tempParam.empty()) { bSubtitleInput = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"input_prefix"), subtitleInputPrefix);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"output_prefix"), subtitleOutputPrefix);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"red"), tempParam);
			if (!tempParam.empty()) { subtitleRedness = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"blue"), tempParam);
			if (!tempParam.empty()) { subtitleBlueness = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"green"), tempParam);
			if (!tempParam.empty()) { subtitleGreenness = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"font_size"), tempParam);
			if (!tempParam.empty()) { subtitleFontSize = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"font"), subtitleFont);
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"x"), tempParam);
			if (!tempParam.empty()) { subtitleX = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"y"), tempParam);
			if (!tempParam.empty()) { subtitleY = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"z"), tempParam);
			if (!tempParam.empty()) { subtitleZ = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"length"), tempParam);
			if (!tempParam.empty()) { subtitleLength = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/subtitle"), wstring(L"blink_delay"), tempParam);
			if (!tempParam.empty()) { subtitleBlinkDelay = WStringToInt(tempParam); tempParam.clear(); }
		}

		// load window options
		if (HasNode(pXMLDom, wstring(L"//headx/render/window"), 0))
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"maximize"), tempParam);
			if (!tempParam.empty()) { bWindowMaximize = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"menu"), tempParam);
			if (!tempParam.empty()) { bWindowMenu = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"border"), tempParam);
			if (!tempParam.empty()) { bWindowBorder = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"top"), tempParam);
			if (!tempParam.empty()) { bWindowOnTop = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"x"), tempParam);
			if (!tempParam.empty()) { windowXPos = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"y"), tempParam);
			if (!tempParam.empty()) { windowYPos = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"width"), tempParam);
			if (!tempParam.empty()) { windowWidth = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"height"), tempParam);
			if (!tempParam.empty()) { windowHeight = WStringToInt(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"title"), windowTitle);
			if (!paramsOnly)
			{
				ReadAttribute(pXMLDom, wstring(L"//headx/render/window"), wstring(L"transparent"), tempParam);
				if (!tempParam.empty()) { bWindowTransparent = WStringToBool(tempParam); tempParam.clear(); }
			}
		}

		
		AddTransforms(GetNode(pXMLDom, wstring(L"//headx/render"), 0), resourceGroup);

		// load rotation options
		/*ReadAttribute(pXMLDom, wstring(L"//headx/render/rotation"), wstring(L"x"), tempParam);
		if (!tempParam.empty()) { rotationStartX = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/rotation"), wstring(L"y"), tempParam);
		if (!tempParam.empty()) { rotationStartY = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/rotation"), wstring(L"z"), tempParam);
		if (!tempParam.empty()) { rotationStartZ = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/rotation"), wstring(L"reset"), tempParam);
		if (!tempParam.empty()) { resetRotation = WStringToBool(tempParam); tempParam.clear(); }

		// load scaling option
		ReadAttribute(pXMLDom, wstring(L"//headx/render/view"), wstring(L"squash"), tempParam);
		if (!tempParam.empty()) { aspectRatio = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/view"), wstring(L"scale"), tempParam);
		if (!tempParam.empty()) { scale = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/view"), wstring(L"fov"), tempParam);
		if (!tempParam.empty()) { fov = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/view"), wstring(L"reset"), tempParam);
		if (!tempParam.empty()) { resetScale = WStringToBool(tempParam); tempParam.clear(); }

		// load translation options
		ReadAttribute(pXMLDom, wstring(L"//headx/render/translation"), wstring(L"x"), tempParam);
		if (!tempParam.empty()) { translationStartX = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/translation"), wstring(L"y"), tempParam);
		if (!tempParam.empty()) { translationStartY = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/translation"), wstring(L"z"), tempParam);
		if (!tempParam.empty()) { translationStartZ = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/render/translation"), wstring(L"reset"), tempParam);
		if (!tempParam.empty()) { resetTranslation = WStringToBool(tempParam); tempParam.clear(); }*/
	}

	if (HasNode(pXMLDom, wstring(L"//headx/action"), 0))
	{
		// load text interaction options
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"echo"), tempParam);
		if (!tempParam.empty()) { bEchoInputs = WStringToBool(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"curlytags"), tempParam);
		if (!tempParam.empty()) { bCurlyTags = WStringToBool(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"breakonstop"), tempParam);
		if (!tempParam.empty()) { bBreakOnStop = WStringToBool(tempParam); tempParam.clear(); }
		// load logfile options
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"logfile"), logFileName);
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"shortcuts"), tempParam);
		if (!tempParam.empty()) { bAllowShortcutKeys = WStringToBool(tempParam); tempParam.clear(); }

		// set default mouse mode
		ReadAttribute(pXMLDom, wstring(L"//headx/action"), wstring(L"mouse"), interactionMode);

		// load Synapse options
		if (HasNode(pXMLDom, wstring(L"//headx/action/synapse"), 0))
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse"), wstring(L"command"), tempParam);
			if (!tempParam.empty()) { bSynapseCommands = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse"), wstring(L"direct"), tempParam);
			if (!tempParam.empty()) { bSynapseDirect = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse"), wstring(L"headmorphs"), tempParam);
			if (!tempParam.empty()) { bSynapseFaceMorphs = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse"), wstring(L"camera"), tempParam);
			if (!tempParam.empty()) { bSynapseCamera = WStringToBool(tempParam); tempParam.clear(); }
			ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse"), wstring(L"block"), tempParam);
			if (!tempParam.empty()) { bBlockSynapse = WStringToBool(tempParam); tempParam.clear(); }

			if (HasNode(pXMLDom, wstring(L"//headx/action/synapse/background"), 0))
			{
				ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse/background"), wstring(L"show"), tempParam);
				if (!tempParam.empty()) { bDynBG = WStringToBool(tempParam); tempParam.clear(); }
				ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse/background"), wstring(L"name"), dynBGSynapse);
				ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse/background"), wstring(L"width"), tempParam);
				if (!tempParam.empty()) { dynBGX = WStringToInt(tempParam); tempParam.clear(); }
				ReadAttribute(pXMLDom, wstring(L"//headx/action/synapse/background"), wstring(L"height"), tempParam);
				if (!tempParam.empty()) { dynBGY = WStringToInt(tempParam); tempParam.clear(); }
			}
		}

		if (HasNode(pXMLDom, wstring(L"//headx/action/expression"), 0))
		{
			if (!pbExpressionsAreCleared[0])
			{
				expressionMap.clear(); // clear only if any are defined
				pbExpressionsAreCleared[0] = true;
			}
			bool expressionLoaded;
			size_t expressionIndex = 0;

			do
			{
				if (expressionLoaded = HasNode(pXMLDom, wstring(L"//headx/action/expression"), expressionIndex))
				{
					HXExpression expression;
					ReadAttribute(pXMLDom, wstring(L"//headx/action/expression"), wstring(L"name"), expression.name, expressionIndex);
					AddBlendshapes(pXMLDom, wstring(L"//headx/action/expression"), expressionIndex, expression.shapes);
					AddMovements(pXMLDom, wstring(L"//headx/action/expression"), expressionIndex, expression.movements);
					ReadAttribute(pXMLDom, wstring(L"//headx/action/expression"), wstring(L"bounce"), tempParam, expressionIndex);
					if (!tempParam.empty()) { expression.bounce = WStringToBool(tempParam); tempParam.clear(); }
					expressionMap[expression.name].push_back(expression);
				}
				expressionIndex++;
			} 
			while (expressionLoaded);
		}

		// load idle options
		if (HasNode(pXMLDom, wstring(L"//headx/action/idle"), 0))
		{
			ReadAttribute(pXMLDom, wstring(L"//headx/action/idle"), wstring(L"motion"), tempParam);
			if (!tempParam.empty()) { bIdleBehaviour = WStringToBool(tempParam); tempParam.clear(); }

			if (HasNode(pXMLDom, wstring(L"//headx/action/idle/expression"), 0))
			{
				if (!pbExpressionsAreCleared[1])
				{
					idleExpressionSets.clear(); // clear only if any are defined
					pbExpressionsAreCleared[1] = true;
				}
				bool expressionLoaded;
				size_t expressionIndex = 0;
				do
				{
					if (expressionLoaded = HasNode(pXMLDom, wstring(L"//headx/action/idle/expression"), expressionIndex))
					{
						HXIdleExpression idleExpr;
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"name"), idleExpr.expression.name, expressionIndex);
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"min_amplitude"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.minAmplitude = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"max_amplitude"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.maxAmplitude = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"min_interval"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.minInterval = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"max_interval"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.maxInterval = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"min_duration"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.minDuration = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"max_duration"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.maxDuration = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"min_hold"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.minHold = WStringToFloat(tempParam); tempParam.clear(); }
						ReadAttribute(pXMLDom, wstring(L"//headx/action/idle/expression"), wstring(L"max_hold"), tempParam, expressionIndex);
						if (!tempParam.empty()) { idleExpr.maxHold = WStringToFloat(tempParam); tempParam.clear(); }

						AddBlendshapes(pXMLDom, wstring(L"//headx/action/idle/expression"), expressionIndex, idleExpr.expression.shapes);
						AddMovements(pXMLDom, wstring(L"//headx/action/idle/expression"), expressionIndex, idleExpr.expression.movements);
						//expressionMap[idleExpr.expression.name].push_back(idleExpr.expression);

						bool isNew = true;
						for (size_t i = 0; i < idleExpressionSets.size(); i++)
						{
							if (idleExpressionSets[i][0].expression.name == idleExpr.expression.name)
							{
								idleExpressionSets[i].push_back(idleExpr);
								isNew = false;
								break;
							}
						}
						if (isNew)
						{
							if (idleExpressionSets.size() >= HXAT_IDS)
							{
								WNotify(NULL, L"Number of different expressions (%d) in idle motion definition exceeds maximum", expressionIndex+1);
								break;
							}
							idleExpressionSets.resize(idleExpressionSets.size()+1);
							idleExpressionSets.back().push_back(idleExpr);
						}
					}
					expressionIndex++;
				} 
				while (expressionLoaded);
			}
		}

	}

	// load quick actions
	if (HasNode(pXMLDom, wstring(L"//headx/quick"), 0))
	{
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id0"), wstring(L"label"), quickAction0_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id1"), wstring(L"label"), quickAction1_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id2"), wstring(L"label"), quickAction2_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id3"), wstring(L"label"), quickAction3_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id4"), wstring(L"label"), quickAction4_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id5"), wstring(L"label"), quickAction5_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id6"), wstring(L"label"), quickAction6_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id7"), wstring(L"label"), quickAction7_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id8"), wstring(L"label"), quickAction8_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id9"), wstring(L"label"), quickAction9_label);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id0"), wstring(L"command"), quickAction0_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id1"), wstring(L"command"), quickAction1_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id2"), wstring(L"command"), quickAction2_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id3"), wstring(L"command"), quickAction3_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id4"), wstring(L"command"), quickAction4_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id5"), wstring(L"command"), quickAction5_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id6"), wstring(L"command"), quickAction6_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id7"), wstring(L"command"), quickAction7_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id8"), wstring(L"command"), quickAction8_speak);
		ReadAttribute(pXMLDom, wstring(L"//headx/quick/id9"), wstring(L"command"), quickAction9_speak);
	}
	
	// speech synthesis 
	if (HasNode(pXMLDom, wstring(L"//headx/TTS"), 0))
	{
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"engine"), voiceEngine);
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"voice"), voiceName);
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"rampfunc"), tempParam);
		if (!tempParam.empty()) { rampFunc = WStringToBlendMode(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"khz"), tempParam);
		if (!tempParam.empty()) { voicekHz = WStringToInt(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"overlap"), tempParam);
		if (!tempParam.empty()) { voiceOverlap = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"rate"), tempParam);
		if (!tempParam.empty()) { voiceRate = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"avsync"), tempParam);
		if (!tempParam.empty()) { voiceAVSync = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"vmodulator"), tempParam);
		if (!tempParam.empty()) { voiceModulator = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"mixframes"), tempParam);
		if (!tempParam.empty()) { mixFrames = WStringToInt(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"mixfactor"), tempParam);
		if (!tempParam.empty()) { mixFactor = WStringToFloat(tempParam); tempParam.clear(); }

		// Chinese language support
		ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"language"), tempParam);
		if (!tempParam.empty()) {
			wstring lowParam = WStringToLowerCase(tempParam);
			if (lowParam == L"chinese") {
				voiceLanguage = Chinese;
			} else { // default is English
				voiceLanguage = English;
			}
		}

		#ifdef _MORPHDEBUG
			ReadAttribute(pXMLDom, wstring(L"//headx/TTS"), wstring(L"dumpfile"), morphDumpFile);
		#endif

		if (HasNode(pXMLDom, wstring(L"//headx/TTS/viseme"), 0))
		{
			if (!pbExpressionsAreCleared[2])
			{
				visemeMap.clear();
				pbExpressionsAreCleared[2] = true;
			}
			bool visemeLoaded;
			size_t visemeIndex = 0;
			do
			{
				if (visemeLoaded = HasNode(pXMLDom, wstring(L"//headx/TTS/viseme"), visemeIndex))
				{
					wstring visemeName;
					ReadAttribute(pXMLDom, wstring(L"//headx/TTS/viseme"), wstring(L"name"), visemeName, visemeIndex);				
					vector<HXVisemeBlendShape> vMap;
					AddBlendshapes(pXMLDom, wstring(L"//headx/TTS/viseme"), visemeIndex, vMap);
					visemeMap[visemeName].push_back(vMap);
					//if (visemeMap.find(visemeName) != visemeMap.end()) {
					//visemeMap[visemeName] = vMap;
					visemeIndex++;
				}
			} while (visemeLoaded);
		}

	}

	success = true; // xml file parsed successfully...

clean:
    if (&var) VariantClear(&var);
	if (wcopy) delete [] wcopy;
    if (pXMLDom) pXMLDom->Release();

    return success;
}

void HXPlayerConfig::AddBlendshapes(IXMLDOMNode *pXMLDom, const wstring& nodeName, int groupIndex, vector<HXVisemeBlendShape>& vbsArray)
{
	IXMLDOMNode* pNode = GetNode(pXMLDom, nodeName, groupIndex);
	if (pNode)
	{
		int numberOfBlendShapes = CountChildNodesWithName(pXMLDom, nodeName, groupIndex, wstring(L"bs"));
		for (int bsi = 0; bsi < numberOfBlendShapes; bsi++)
		{
			vbsArray.push_back(HXVisemeBlendShape());
			ReadAttribute(pNode, wstring(L"bs"), wstring(L"name"), vbsArray.back().name, bsi);
			wstring tempParam;
			ReadAttribute(pNode, wstring(L"bs"), wstring(L"amplitude"), tempParam, bsi);
			if (!tempParam.empty()) { vbsArray.back().amplitude = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"bs"), wstring(L"offset"), tempParam, bsi);
			if (!tempParam.empty()) { vbsArray.back().offset = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"bs"), wstring(L"duration"), tempParam, bsi);
			if (!tempParam.empty()) { vbsArray.back().duration = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"bs"), wstring(L"rampfunc"), tempParam, bsi);
			if (!tempParam.empty()) { vbsArray.back().rampFunc = WStringToBlendMode(tempParam); tempParam.clear(); }
		}
	}
}

void HXPlayerConfig::AddMovements(IXMLDOMNode *pXMLDom, const wstring& nodeName, int groupIndex, vector<HXMovement>& movArray)
{
	IXMLDOMNode* pNode = GetNode(pXMLDom, nodeName, groupIndex);
	if (pNode)
	{
		size_t index = 0;
		while (HasNode(pNode, wstring(L"rotation"), index))
		{
			movArray.push_back(HXMovement(HXMOV_ROTATION));
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"group"), movArray.back().group, index);
			wstring tempParam;
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"x"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().x = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"y"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().y = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"z"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().z = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"offset"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().offset = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"duration"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().duration = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"rotation"), wstring(L"rampfunc"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().rampFunc = WStringToBlendMode(tempParam); tempParam.clear(); }
			index++;
		}
		index = 0;
		while (HasNode(pNode, wstring(L"translation"), index))
		{
			movArray.push_back(HXMovement(HXMOV_TRANSLATION));
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"group"), movArray.back().group, index);
			wstring tempParam;
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"x"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().x = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"y"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().y = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"z"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().z = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"offset"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().offset = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"duration"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().duration = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"translation"), wstring(L"rampfunc"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().rampFunc = WStringToBlendMode(tempParam); tempParam.clear(); }
			index++;
		}
		index = 0;
		while (HasNode(pNode, wstring(L"scale"), index))
		{
			movArray.push_back(HXMovement(HXMOV_SCALE));
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"group"), movArray.back().group, index);
			wstring tempParam;
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"x"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().x = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"y"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().y = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"z"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().z = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"offset"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().offset = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"duration"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().duration = WStringToFloat(tempParam); tempParam.clear(); }
			ReadAttribute(pNode, wstring(L"scale"), wstring(L"rampfunc"), tempParam, index);
			if (!tempParam.empty()) { movArray.back().rampFunc = WStringToBlendMode(tempParam); tempParam.clear(); }
			index++;
		}
	}
}

void HXPlayerConfig::BlendModelLoad(IXMLDOMNode *pXMLDom, HXResource& res)
{
	res.blendList.clear();
	// shapes...
	bool bLoaded;	
	size_t bIndex = 0;
	do
	{
		if (bLoaded = HasNode(pXMLDom, wstring(L"bs"), bIndex))
		{
			wstring blendName, blendFile;
			ReadAttribute(pXMLDom, wstring(L"bs"), wstring(L"name"), blendName, bIndex);
			ReadAttribute(pXMLDom, wstring(L"bs"), wstring(L"file"), blendFile, bIndex);
			res.blendList.push_back(HXBlend(blendName, blendFile, false));
			bIndex++;
		}
	} while (bLoaded);
	// materials...
	bIndex = 0;
	do
	{
		if (bLoaded = HasNode(pXMLDom, wstring(L"bt"), bIndex))
		{
			wstring blendName, blendFile;
			ReadAttribute(pXMLDom, wstring(L"bt"), wstring(L"name"), blendName, bIndex);
			ReadAttribute(pXMLDom, wstring(L"bt"), wstring(L"file"), blendFile, bIndex);
			res.blendList.push_back(HXBlend(blendName, blendFile, true));
			bIndex++;
		}
	} while (bLoaded);
}

//! Load 3D models from the XML configuration at the specified filepath.
size_t HXPlayerConfig::AddModelsFromXML(wstring& xmlstring, bool isFilename, bool ignoreImports)
{
	size_t loaded = 0;
	IXMLDOMDocument *pXMLDom=NULL;
    IXMLDOMNodeList *pNodes=NULL;
    VARIANT_BOOL status;
    VARIANT var;
	wchar_t* wcopy = NULL;
    HRESULT hr;
	wstring tempParam;

    pXMLDom = DomFromCOM();
    if (!pXMLDom) goto clean;

    VariantInit(&var);
   
	if (isFilename)
	{
		var = VariantString(xmlstring.c_str());
		HRCALL(pXMLDom->load(var, &status), "dom->load(): ");
	}
	else
	{
		wchar_t* wcopy = new wchar_t[xmlstring.size()+1];
		for (size_t i = 0; i < xmlstring.size(); i++)
		{
			wcopy[i] = xmlstring[i];
		}
		wcopy[xmlstring.size()] = '\0';		
		HRCALL(pXMLDom->loadXML(wcopy, &status), "dom->loadXML(): ");
	}

    if (status!=VARIANT_TRUE) 
	{
          ReportParseError(pXMLDom, "Failed to load HXML model.");
          goto clean;
    }

	if (!ignoreImports)
	{
		// import from other HXML/AXML/PXML files
		bool importLoaded;
		size_t importIndex = 0;
		do
		{
			if (importLoaded = HasNode(pXMLDom, wstring(L"//headx/import"), importIndex))
			{
				ReadAttribute(pXMLDom, wstring(L"//headx/import"), wstring(L"file"), tempParam, importIndex);
				loaded += AddModelsFromXML(tempParam, true); // no cancel
				importIndex++;
			}
		} while (importLoaded);
	}

	// load all models:
	IXMLDOMNode* pNode;
	size_t index = 0;
	while (pNode = GetNode(pXMLDom, wstring(L"//headx/render"), index++))
	{
		loaded += AddModelsFromNode(pNode, resourceGroup);
	}

clean:
    if (&var) VariantClear(&var);
	if (wcopy) delete [] wcopy;
    if (pXMLDom) pXMLDom->Release();
    if (pNodes) pNodes->Release();

    return loaded;
}

size_t HXPlayerConfig::AddModelsFromNode(IXMLDOMNode* pXMLDom, HXResourceGroup& parentGroup)
{
	wstring tempParam;
	size_t index = 0;
	size_t loaded = 0;

	while (ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"file"), tempParam, index))
	{
		parentGroup.resources.push_back(HXResource(tempParam));
		parentGroup.resources.back().pGroup = &parentGroup;
		tempParam.clear();
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"texture"), parentGroup.resources.back().textureName, index);
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"detail"), parentGroup.resources.back().detailName, index);
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"shiny"), tempParam, index);
		if (!tempParam.empty()) { parentGroup.resources.back().shininess = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"alpha_threshold"), tempParam, index);
		if (!tempParam.empty()) { parentGroup.resources.back().alpha_threshold = WStringToFloat(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"center"), tempParam, index);
		if (!tempParam.empty()) { parentGroup.resources.back().center = WStringToBool(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"recomp_normal"), tempParam, index);
		if (!tempParam.empty()) { parentGroup.resources.back().recomp_normal = WStringToInt(tempParam); tempParam.clear(); }
		ReadAttribute(pXMLDom, wstring(L"model"), wstring(L"index"), tempParam, index);
		if (!tempParam.empty()) { parentGroup.resources.back().item = WStringToInt(tempParam); tempParam.clear(); }
		BlendModelLoad(GetNode(pXMLDom, wstring(L"model"), index), parentGroup.resources.back());
		++loaded;
		++index;
	}

	IXMLDOMNode* pNode;
	index = 0;
	while (pNode = GetNode(pXMLDom, wstring(L"group"), index))
	{
		if (ReadAttribute(pXMLDom, wstring(L"group"), wstring(L"name"), tempParam, index))
		{
			wstring name = WStringToLowerCase(tempParam);
			HXResourceGroup* pGroup = parentGroup.GetChild(name);
			if (!pGroup)
			{
				parentGroup.children.push_back(HXResourceGroup(name, &parentGroup));
				pGroup = &parentGroup.children.back();
			}
			loaded += AddModelsFromNode(pNode, *pGroup);
			AddTransforms(pNode, *pGroup);
		}
		++index;
	}

	return loaded;
}

void HXPlayerConfig::AddTransforms(IXMLDOMNode* pXMLDom, HXResourceGroup& rg)
{
	wstring tempParam;

	// load rotation options
	ReadAttribute(pXMLDom, wstring(L"rotation"), wstring(L"x"), tempParam);
	if (!tempParam.empty()) { rg.transform.rotX = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"rotation"), wstring(L"y"), tempParam);
	if (!tempParam.empty()) { rg.transform.rotY = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"rotation"), wstring(L"z"), tempParam);
	if (!tempParam.empty()) { rg.transform.rotZ = WStringToFloat(tempParam); tempParam.clear(); }

	// load scaling option
	ReadAttribute(pXMLDom, wstring(L"scale"), wstring(L"x"), tempParam);
	if (!tempParam.empty()) { rg.transform.scaleX = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"scale"), wstring(L"y"), tempParam);
	if (!tempParam.empty()) { rg.transform.scaleY = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"scale"), wstring(L"z"), tempParam);
	if (!tempParam.empty()) { rg.transform.scaleZ = WStringToFloat(tempParam); tempParam.clear(); }

	// load translation options
	ReadAttribute(pXMLDom, wstring(L"translation"), wstring(L"x"), tempParam);
	if (!tempParam.empty()) { rg.transform.transX = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"translation"), wstring(L"y"), tempParam);
	if (!tempParam.empty()) { rg.transform.transY = WStringToFloat(tempParam); tempParam.clear(); }
	ReadAttribute(pXMLDom, wstring(L"translation"), wstring(L"z"), tempParam);
	if (!tempParam.empty()) { rg.transform.transZ = WStringToFloat(tempParam); tempParam.clear(); }

	rg.transform.mvmIsCurrent = false;
}

//! Clear all configuration data (set to default parameters).
void HXPlayerConfig::Clear()
{
	resourceGroup.Clear();
	bImmediateModeRender = false;
	bUseShaders = true;
	FGfileName.clear();
	bApplyFGDetail = true;
	FGmorphDuration = 3.0f;
	FGmorphFlags.clear();

	renderFPS = 50;
	maxDepthPasses = 12;
	backgroundName.clear();
	bgRedness = 0.0f;
	bgGreenness = 0.0f;
	bgBlueness = 0.0f;
	light1.Clear();
	light2.Clear();

	bIdleBehaviour = false;
	bCurlyTags = true;
	bBreakOnStop = false;
	bEchoInputs = true;
	exprFileName.clear();
	logFileName.clear();
	bAllowShortcutKeys = true;
	bSynapseCommands = true;
	bSynapseDirect = false;
	bSynapseFaceMorphs = false;
	bSynapseCamera = false;
	bBlockSynapse = false;
	bDynBG = false;
	dynBGSynapse.clear();
	dynBGX = 0;
	dynBGY = 0;
	bWindowMaximize = false;
	bWindowMenu = true;
	bWindowBorder = true;
	bWindowOnTop = false;
	bWindowTransparent = false;
	windowXPos = -1;
	windowYPos = -1;
	windowWidth = -1;
	windowHeight = -1;
	windowTitle.clear();
	interactionMode.clear();
	fov = 35.0f;
	expressionMap.clear();
	idleExpressionSets.clear();

	bSubtitleOutput = false;
	bSubtitleInput = false;
	subtitleInputPrefix.clear();
	subtitleOutputPrefix.clear();
	subtitleRedness = 1.0f;
	subtitleBlueness = 1.0f;
	subtitleGreenness = 1.0f;
	subtitleFontSize = SUBTITLE_DEFAULT_FONTSIZE;
	subtitleFont.clear();
	subtitleX = SUBTITLE_DEFAULT_XPOS;
	subtitleY = SUBTITLE_DEFAULT_YPOS;
	subtitleZ = SUBTITLE_DEFAULT_ZPOS;
	subtitleLength = SUBTITLE_DEFAULT_LENGTH;
	subtitleBlinkDelay = SUBTITLE_DEFAULT_BLINKRATE;

	quickAction0_label.clear();
	quickAction1_label.clear();
	quickAction2_label.clear();
	quickAction3_label.clear();
	quickAction4_label.clear();
	quickAction5_label.clear();
	quickAction6_label.clear();
	quickAction7_label.clear();
	quickAction8_label.clear();
	quickAction9_label.clear();
	quickAction0_speak.clear();
	quickAction1_speak.clear();
	quickAction2_speak.clear();
	quickAction3_speak.clear();
	quickAction4_speak.clear();
	quickAction5_speak.clear();
	quickAction6_speak.clear();
	quickAction7_speak.clear();
	quickAction8_speak.clear();
	quickAction9_speak.clear();
	
	// TTS
	voiceName.clear();
	voiceEngine = L"SAPI";
	voiceLanguage = English;
	voicekHz = 22;
	voiceRate = 0.0f;
	voiceAVSync = 0.0f;
	voiceModulator = 0.65f;
	mixFrames = 2;
	mixFactor = 0.5f;
	rampFunc = HXBM_ANYBLEND;
	voiceOverlap = 0.75;
	visemeMap.clear();

	#ifdef _MORPHDEBUG
		morphDumpFile.clear();
	#endif
}

IXMLDOMDocument* HXPlayerConfig::DomFromCOM()
{
    HRESULT hr;
    IXMLDOMDocument *pxmldoc = NULL;

    //HRCALL( hr = CoCreateInstance(__uuidof(DOMDocument60), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pxmldoc)), "Create a new DOMDocument");
	HRCALL( hr = CoCreateInstance(__uuidof(DOMDocument), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pxmldoc)), "Create a new DOMDocument");

    if (SUCCEEDED(hr))
    {
		HRCALL( pxmldoc->put_async(VARIANT_FALSE), "should never fail");
		HRCALL( pxmldoc->put_validateOnParse(VARIANT_FALSE), "should never fail");
		HRCALL( pxmldoc->put_resolveExternals(VARIANT_FALSE), "should never fail");
	}

    return pxmldoc;
clean:
    if (pxmldoc)
    {
        pxmldoc->Release();
    }
    return NULL;
}

VARIANT HXPlayerConfig::VariantString(BSTR str)
{
    VARIANT var;
    VariantInit(&var);
    V_BSTR(&var) = SysAllocString(str);
    V_VT(&var) = VT_BSTR;
    return var;
}

VARIANT HXPlayerConfig::VariantString(const wstring& str)
{
    VARIANT var;
    VariantInit(&var);
    V_BSTR(&var) = SysAllocString(str.c_str());
    V_VT(&var) = VT_BSTR;
    return var;
}

void HXPlayerConfig::ReportParseError(IXMLDOMDocument *pDom, char *desc) 
{
    IXMLDOMParseError *pXMLErr=NULL;
    BSTR bstrReason = NULL;
    HRESULT hr;
    HRCALL(pDom->get_parseError(&pXMLErr),
                "dom->get_parseError: ");
    HRCALL(pXMLErr->get_reason(&bstrReason),
                "parseError->get_reason: ");

	ANotify(NULL, "Error: %s\nReason: %S\n", desc, bstrReason);
clean:
    if (pXMLErr) pXMLErr->Release();
    if (bstrReason) SysFreeString(bstrReason);
}

//! Count the named children of a named node.
long HXPlayerConfig::CountChildNodesWithName(IXMLDOMNode *pXMLDom, const wstring& nodeName, int index, const wstring& childName)
{
	IXMLDOMNodeList *pNodes=NULL;
	IXMLDOMNodeList *pChildNodes=NULL;
	IXMLDOMNode *pNode = NULL;
	IXMLDOMNode *pSubNode = NULL;
	BSTR bstr = NULL;
	HRESULT hr;
    long length;
	long count = 0;

	// Query a node-set.	
	bstr = SysAllocString(nodeName.c_str());
    HRCALL(pXMLDom->selectNodes(bstr, &pNodes), "selectNodes ");
    if (!pNodes) 
	{
		IXMLDOMDocument* pXMLDomDoc;
		pXMLDom->get_ownerDocument(&pXMLDomDoc);
		ReportParseError(pXMLDomDoc, "Cannot select XML nodes.");
    }
    else 
	{
        HRCALL(pNodes->get_length(&length), "get_length: ");
		if (index < length)
		{
			HRCALL(pNodes->get_item(index, &pNode), "get_item: ");
			HRCALL(pNode->get_childNodes(&pChildNodes), "get_childNodes: ");
			while (pChildNodes->nextNode(&pSubNode) == S_OK)
			{
				if (bstr) SysFreeString(bstr);
				pSubNode->get_nodeName(&bstr);
				if (wcscmp(childName.c_str(), bstr) == 0)
				{
					count++;
				}
			}
		}
	}

clean:
	if (bstr) SysFreeString(bstr);
	if (pNodes) pNodes->Release();
	if (pNode) pNode->Release();
	if (pSubNode) pSubNode->Release();
	return count;
}

//! Count the number of nodes of a given name.
long HXPlayerConfig::CountNodes(IXMLDOMNode *pXMLDom, const wstring& nodeName)
{
	IXMLDOMNodeList *pNodes = NULL;
	BSTR bstr = NULL;
	HRESULT hr;
    long length = 0;

	// Query a node-set.
	bstr = SysAllocString(nodeName.c_str());
    HRCALL(pXMLDom->selectNodes(bstr, &pNodes), "selectNodes ");
    if (!pNodes) 
	{
        IXMLDOMDocument* pXMLDomDoc;
		pXMLDom->get_ownerDocument(&pXMLDomDoc);
		ReportParseError(pXMLDomDoc, "Cannot select XML nodes.");
    }
    else 
	{
        HRCALL(pNodes->get_length(&length), "get_length: ");
	}

clean:
	if (bstr) SysFreeString(bstr);
	if (pNodes) pNodes->Release();
	return length;
}

//! Check whether a node with the given name and number exists.
bool HXPlayerConfig::HasNode(IXMLDOMNode *pXMLDom, const wstring& nodeName, int index)
{
	return (index < CountNodes(pXMLDom, nodeName));
}

IXMLDOMNode* HXPlayerConfig::GetNode(IXMLDOMNode *pXMLDom, const wstring& nodeName, int index)
{
	IXMLDOMNodeList *pNodes = NULL;
	IXMLDOMNode *pNode = NULL;
	BSTR bstr = NULL;
	HRESULT hr;
	long length = 0;

	// Query a node-set.
	bstr = SysAllocString(nodeName.c_str());
    HRCALL(pXMLDom->selectNodes(bstr, &pNodes), "selectNodes ");
	if (bstr) SysFreeString(bstr);
    if (!pNodes) 
	{
        IXMLDOMDocument* pXMLDomDoc;
		pXMLDom->get_ownerDocument(&pXMLDomDoc);
		ReportParseError(pXMLDomDoc, "Cannot select XML nodes.");
    }
	else
	{
		HRCALL(pNodes->get_length(&length), "get_length: ");
		if (index < length)
		{
			HRCALL(pNodes->get_item(index, &pNode), "get_item: ");
		}
	}

clean:
	if (bstr) SysFreeString(bstr);
	if (pNodes) pNodes->Release();
	return pNode;	
}

//! Read a named attribute from a named, numbered node in a nodeList.
bool HXPlayerConfig::ReadAttribute(IXMLDOMNode *pXMLDom, const wstring& nodeName, const wstring& attrName, wstring& attrValue, int index)
{
	IXMLDOMNodeList *pNodes=NULL;
	IXMLDOMNode *pNode = NULL;
	IXMLDOMNode *pSubNode = NULL;
	IXMLDOMNamedNodeMap *pMap = NULL;
	BSTR bstr = NULL;
	VARIANT var;
	bool success = false;
	HRESULT hr;
    long length;

	VariantInit(&var);

	// Query a node-set.
	bstr = SysAllocString(nodeName.c_str());
    HRCALL(pXMLDom->selectNodes(bstr, &pNodes), "selectNodes ");
    if (!pNodes) 
	{
        IXMLDOMDocument* pXMLDomDoc;
		pXMLDom->get_ownerDocument(&pXMLDomDoc);
		ReportParseError(pXMLDomDoc, "Cannot select XML nodes.");
    }
    else 
	{
        HRCALL(pNodes->get_length(&length), "get_length: ");
		if (index < length)
		{
			HRCALL(pNodes->get_item(index, &pNode), "get_item: ");
			if (bstr) SysFreeString(bstr);
			bstr = SysAllocString(attrName.c_str());
			HRCALL(pNode->get_attributes(&pMap)," get_attributes ");
			if (pMap)
			{
				HRCALL(pMap->getNamedItem(bstr, &pSubNode), " getNamedItem ");
				if (pSubNode)
				{
					HRCALL(pSubNode->get_nodeValue(&var), " get_nodeValue ");
					//WNotify(NULL, L"XML %s value read: %s", nodeName.c_str(), V_BSTR(&var));
					attrValue = wstring(V_BSTR(&var));
					success = true;
				}
			}
		}
	}

clean:
	if (bstr) SysFreeString(bstr);
	if (&var) VariantClear(&var);
	if (pNodes) pNodes->Release();
	if (pNode) pNode->Release();
	if (pSubNode) pSubNode->Release();
	return success;
}

//-----------------------------------------------------------------------------